package com.stark.wechat.service.material;

import com.dtflys.forest.Forest;
import com.dtflys.forest.http.ForestRequest;
import com.dtflys.forest.http.ForestResponse;
import com.dtflys.forest.multipart.ByteArrayMultipart;
import com.fasterxml.jackson.databind.JsonNode;
import com.stark.wechat.core.Config;
import com.stark.wechat.core.File;
import com.stark.wechat.exception.WechatException;
import com.stark.wechat.service.material.model.*;
import com.stark.wechat.util.JacksonUtils;
import com.stark.wechat.util.ResponseUtils;
import lombok.Builder;
import org.apache.commons.lang3.StringUtils;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.requireNonNull;

/**
 * 永久素材接口
 *
 * @author <a href="mailto:mengbin@hotmail.com">Ben</a>
 * @version 1.0.0
 * @see <a href="https://developers.weixin.qq.com/doc/service/guide/product/asset.html">能力接入 / 素材管理</a>
 * @since 2024/5/26
 */
@Builder
public class PermanentMaterialService {

    private final Config config;

    /**
     * 获取永久素材
     * <p>本接口用于根据media_id获取永久素材的详细信息
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param mediaId     要获取的素材的media_id
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/material/permanent/api_getmaterial.html">素材管理 / 永久素材 / 获取永久素材</a>
     */
    public Object getMaterial(String accessToken, String mediaId) {
        requireNonNull(accessToken);
        requireNonNull(mediaId);

        @SuppressWarnings("rawtypes")
        ForestResponse response = Forest
                .post(config.getWechatGateway() + "/cgi-bin/material/get_material")
                .contentTypeJson()
                .addQuery("access_token", accessToken)
                .addBody("media_id", mediaId)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsResponse();
        // 文件
        String contentDisposition = response.getHeaderValue("content-disposition");
        if (StringUtils.isNotBlank(contentDisposition)) {
            String filename = StringUtils.substringBetween(contentDisposition, "filename=\"", "\"");
            String contentType = null;
            String suffix = StringUtils.substringAfterLast(filename, ".").toLowerCase();
            switch (suffix) {
                case "bmp":
                    contentType = "image/bmp";
                    break;
                case "png":
                    contentType = "image/png";
                    break;
                case "jpeg":
                case "jpg":
                    contentType = "image/jpeg";
                    break;
                case "gif":
                    contentType = "image/gif";
                    break;
                case "mp3":
                    contentType = "audio/mp3";
                    break;
                case "wma":
                    contentType = "audio/wma";
                    break;
                case "wav":
                    contentType = "audio/wav";
                    break;
                case "amr":
                    contentType = "audio/amr";
                    break;
                case "mp4":
                    contentType = "video/mp4";
                    break;
            }
            try {
                return new File(response.getByteArray(), filename, contentType);
            } catch (Exception e) {
                throw new WechatException("读取文件流失败", e);
            }
        }
        String responseStr = response.getContent();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("获取永久素材", root, CODE_MAP.get("getMaterial"));
        // 图文消息素材： {"news_item": [{"title": "TITLE","thumb_media_id": "THUMB_MEDIA_ID","show_cover_pic": 1,"author": "AUTHOR","digest": "DIGEST","content": "CONTENT","url": "URL","content_source_url": "CONTENT_SOURCE_URL"}]}
        if (root.has("news_item")) {
            return JacksonUtils.deserialize(responseStr, GetNewsMaterialResponsePayload.class);
        }
        // 视频消息素材： { "title":TITLE, "description":DESCRIPTION, "down_url":DOWN_URL, }
        if (root.has("title")) {
            return JacksonUtils.deserialize(responseStr, GetVideoMaterialResponsePayload.class);
        }
        throw new WechatException("无法解析的媒体类型: " + response.getContentType());
    }

    /**
     * 获取永久素材总数
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @return 素材总数信息
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/material/permanent/api_getmaterialcount.html">素材管理 / 永久素材 / 获取永久素材总数</a>
     */
    public GetMaterialCountResponsePayload getMaterialCount(String accessToken) {
        requireNonNull(accessToken);

        String responseStr = Forest
                .get(config.getWechatGateway() + "/cgi-bin/material/get_materialcount")
                .addQuery("access_token", accessToken)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("获取永久素材总数", root, CODE_MAP.get("getMaterialCount"));
        return JacksonUtils.deserialize(responseStr, GetMaterialCountResponsePayload.class);
    }

    /**
     * 获取永久素材列表
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param type        素材的类型
     * @param offset      从全部素材的该偏移位置开始返回，0表示从第一个素材返回
     * @param count       返回素材的数量，取值在1到20之间
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/material/permanent/api_batchgetmaterial.html">素材管理 / 永久素材 / 获取永久素材列表</a>
     */
    public Object batchGetMaterial(String accessToken, MaterialType type, Integer offset, Integer count) {
        requireNonNull(accessToken);
        requireNonNull(type);
        requireNonNull(offset);
        requireNonNull(count);

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/material/batchget_material")
                .contentTypeJson()
                .addQuery("access_token", accessToken)
                .addBody("type", type)
                .addBody("offset", offset)
                .addBody("count", count)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("获取永久素材列表", root, CODE_MAP.get("batchGetMaterial"));
        // 图文素材
        if (MaterialType.news.equals(type)) {
            return JacksonUtils.deserialize(responseStr, BatchGetNewsMaterialResponsePayload.class);
        }
        // 图片、语音、视频素材
        return JacksonUtils.deserialize(responseStr, BatchGetMaterialResponsePayload.class);
    }

    /**
     * 上传图文消息图片
     * <ol>
     *     <li>该接口所上传的图片，不占用公众号的素材库中图片数量的100000个的限制，图片仅支持jpg/png格式，大小必须在1MB以下。</li>
     *     <li>图文消息支持正文中插入自己账号和其他公众号已群发文章链接的能力。</li>
     * </ol>
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param media       媒体文件
     * @return 图片URL
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/material/permanent/api_uploadimage.html">素材管理 / 永久素材 / 上传图文消息图片</a>
     */
    public String uploadImage(String accessToken, File media) {
        requireNonNull(accessToken);
        requireNonNull(media);

        ByteArrayMultipart file = new ByteArrayMultipart()
                .setData(media.getBytes())
                .setName("media")
                .setFileName(media.getFilename())
                .setContentType(media.getContentType());

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/media/uploadimg")
                .contentTypeMultipartFormData()
                .addQuery("access_token", accessToken)
                .addMultipart(file)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("上传图文消息图片", root, CODE_MAP.get("uploadImage"));
        return root.get("url").asText();
    }

    /**
     * 上传永久素材
     * <ol>
     *     <li>永久图片素材新增后，将带有URL返回给开发者，开发者可以在腾讯系域名内使用（腾讯系域名外使用，图片将被屏蔽）。</li>
     *     <li>公众号的素材库保存总数量有上限：图文消息素材、图片素材上限为100000，其他类型为1000。</li>
     *     <li>
     *         素材的格式大小等要求与公众平台官网一致：
     *         <ul>
     *             <li>图片（image）: 10M，支持bmp/png/jpeg/jpg/gif格式</li>
     *             <li>语音（voice）：2M，播放长度不超过60s，mp3/wma/wav/amr格式</li>
     *             <li>视频（video）：10MB，支持MP4格式</li>
     *             <li>缩略图（thumb）：64KB，支持JPG格式</li>
     *         </ul>
     *     </li>
     *     <li>图文消息的具体内容中，微信后台将过滤外部的图片链接，图片url需通过上传图文消息图片接口上传图片获取。</li>
     *     <li>上传图文消息图片接口所上传的图片，不占用公众号的素材库中图片数量的100000个的限制，图片仅支持jpg/png格式，大小必须在1MB以下。</li>
     *     <li>图文消息支持正文中插入自己账号和其他公众号/服务号已群发文章链接的能力。</li>
     * </ol>
     *
     * @param accessToken  接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param type         媒体文件类型
     * @param media        媒体文件
     * @param title        视频标题
     * @param introduction 视频简介
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/material/permanent/api_addmaterial.html">素材管理 / 永久素材 / 上传永久素材</a>
     */
    public AddMaterialResponsePayload addMaterial(String accessToken, MaterialType type, File media,
                                                  String title, String introduction) {
        requireNonNull(accessToken);
        requireNonNull(type);
        requireNonNull(media);

        ByteArrayMultipart file = new ByteArrayMultipart()
                .setData(media.getBytes())
                .setName("media")
                .setFileName(media.getFilename())
                .setContentType(media.getContentType());
        Map<String, Object> description = new HashMap<>();
        description.put("title", title);
        description.put("introduction", introduction);

        ForestRequest<?> request = Forest
                .post(config.getWechatGateway() + "/cgi-bin/material/add_material")
                .contentTypeMultipartFormData()
                .addQuery("access_token", accessToken)
                .addQuery("type", type)
                .addMultipart(file);
        // 素材描述信息，上传视频素材时需要
        if (MaterialType.video.equals(type)) {
            request.addBody("description", JacksonUtils.serialize(description));
        }
        request
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout());
        String responseStr = request.executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("上传永久素材", root, CODE_MAP.get("addMaterial"));
        return JacksonUtils.deserialize(responseStr, AddMaterialResponsePayload.class);
    }

    /**
     * 删除永久素材
     * <ol>
     *     <li>请谨慎操作本接口，可以删除官网素材管理模块中的图文/语音/视频等素材（需先通过获取素材列表获取media_id）</li>
     *     <li>临时素材无法通过本接口删除</li>
     *     <li>调用该接口需https协议</li>
     * </ol>
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param mediaId     要删除的素材的media_id
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/material/permanent/api_delmaterial.html">素材管理 / 永久素材 / 删除永久素材</a>
     */
    public void delMaterial(String accessToken, String mediaId) {
        requireNonNull(accessToken);
        requireNonNull(mediaId);

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/material/del_material")
                .contentTypeJson()
                .addQuery("access_token", accessToken)
                .addBody("media_id", mediaId)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("删除永久素材", root, CODE_MAP.get("delMaterial"));
    }

    private static final Map<String, Map<Long, String>> CODE_MAP;

    static {
        CODE_MAP = new HashMap<>();

        Map<Long, String> codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请开稍候再试");
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        codeMap.put(40007L, "无效的媒体ID");
        CODE_MAP.put("getMaterial", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请开稍候再试");
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        CODE_MAP.put("getMaterialCount", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请开稍候再试");
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        codeMap.put(40004L, "不合法的媒体文件类型");
        codeMap.put(40007L, "无效媒体ID错误");
        CODE_MAP.put("batchGetMaterial", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(40005L, "上传素材文件格式不对");
        codeMap.put(40009L, "图片尺寸太大");
        CODE_MAP.put("uploadImage", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(40007L, "无效媒体ID");
        CODE_MAP.put("addMaterial", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请稍候再试");
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        codeMap.put(40007L, "不合法的媒体文件 id");
        CODE_MAP.put("delMaterial", Collections.unmodifiableMap(codeMap));
    }

}
